package contegor.scire.structure.queries;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import contegor.scire.core.Connectable;
import contegor.scire.core.Container;
import contegor.scire.core.Id;
import contegor.scire.core.Identifiable;
import contegor.scire.core.InfiniteSingletonList;
import contegor.scire.core.Link;
import contegor.scire.core.NeedsPrecedent;
import contegor.scire.core.ReturnPoint;
import contegor.scire.core.Task;
import contegor.scire.core.TaskId;
import contegor.scire.core.Version;
import contegor.scire.core.WithSubsequentTasks;
import contegor.scire.core.queries.Condition;
import contegor.scire.core.queries.Query;

/**
 * Recursive {@link Query}.
 *
 * @version $Revision$
 * @author  Héctor Cirbián Pousa ($Author$)
 */
@Version("$Id$")
public class RecursiveQuery<C extends Connectable> extends Query<C> implements WithSubsequentTasks<C, C>, NeedsPrecedent<C, C> {
  private static final long serialVersionUID = 4782450933708571130L;

  private Replicator<? extends CircularQuery<C>> recurseReplicator;
  private Condition<C> select;
  private Condition<C> accept;

  private List<C>         precedentResults = null;
  private Map<Id,List<C>> results          = null;

  public RecursiveQuery(Id containerId, Replicator<? extends CircularQuery<C>> recurseReplicator, Condition<C> select, Condition<C> accept) {
    super(containerId);
    this.recurseReplicator = recurseReplicator;
    this.select = select;
    this.accept = accept;
  }

  private RecursiveQuery(TaskId taskId, Id containerId, Replicator<? extends CircularQuery<C>> recurseReplicator, Condition<C> select, Condition<C> accept) {
    super(taskId, containerId);
    this.recurseReplicator = recurseReplicator;
    this.select = select;
    this.accept = accept;
  }

  @Override
  public void process(Container targetContainer) {
    for (C precedentResult: precedentResults) {
      if (precedentResult.isVisibleTo(this.taskId.getTransactionId())) {
        if (this.accept.eval(precedentResult))
          this.returnPoint.manage(this.taskId, precedentResult);
        if (this.select.eval(precedentResult)) { // the same precedentResult can be accepted and selected. So else is wrong.
          if (this.results == null)
            this.results = new TreeMap<Id, List<C>>();
          Connectable aux = precedentResult;
          if (aux instanceof Link) {
            do {
              aux = ((Link)aux).getSource();
            }
            while (aux instanceof Link);
          }
          if (aux instanceof Identifiable) {
            Id targetId = ((Identifiable)aux).getId().getParentId();
            List<C> targetResults = this.results.get(targetId);
            if (targetResults == null) {
              targetResults = new LinkedList<C>();
              this.results.put(targetId, targetResults);
            }
            targetResults.add(precedentResult);
          }      
        }
      }
    }
  }

  @Override
  public List<? extends Collection<C>> getResultsForSubsequentTasks() {
    if ((this.results == null) || (this.results.size() == 0))
      return Collections.emptyList();
    return new ArrayList<List<C>>(this.results.values());
  }

  @Override
  public List<? extends NeedsPrecedent<? super C, C>> getSubsequentTasks() {
    if ((this.results == null) || (this.results.size() == 0))
      return Collections.emptyList();
    List<NeedsPrecedent<C,C>> subs = new ArrayList<NeedsPrecedent<C,C>>(this.results.size());
    TaskId subTaskId = this.taskId;
    for (Id id: this.results.keySet())
      subs.add(recurseReplicator.replicate(subTaskId = subTaskId.next(subTaskId == this.taskId), id).withIt(this.replicate(subTaskId.next(true), id)));
    return subs;
  }

  @Override
  public List<? extends ReturnPoint<? super C>> getSubsequentTasksReturnPoints() {
    if ((this.results == null) || (this.results.size() == 0))
      return Collections.emptyList();
    return new InfiniteSingletonList<ReturnPoint<? super C>>(this.returnPoint, this.results.size());
  }

  @Override
  public void setPrecedent(Task<? extends C> task, Collection<? extends C> results) {
    this.precedentResults = new ArrayList<C>(results);
  }

  @SuppressWarnings("unchecked")
  public RecursiveQuery<C> replicate(TaskId taskId, Id containerId) {
    return new RecursiveQuery<C>(taskId, containerId, (Replicator<CircularQuery<C>>)this.recurseReplicator.clone(), this.select.clone(), this.accept.clone());
  }
}